/* -*- P4_16 -*-
 * Dual-mode: IPv4 forwarding + Source Routing (0x1234)
 * Target: v1model / BMv2 simple_switch
 */

#include <core.p4>
#include <v1model.p4>

typedef bit<48> mac_addr_t;
typedef bit<32> ipv4_addr_t;
typedef bit<9>  egressSpec_t;

/* -------- Headers -------- */
header ethernet_t {
    mac_addr_t dstAddr;
    mac_addr_t srcAddr;
    bit<16>    etherType;
}

header ipv4_t {
    bit<4>    version;
    bit<4>    ihl;
    bit<8>    diffserv;
    bit<16>   totalLen;
    bit<16>   identification;
    bit<3>    flags;
    bit<13>   fragOffset;
    bit<8>    ttl;
    bit<8>    protocol;
    bit<16>   hdrChecksum;
    ipv4_addr_t srcAddr;
    ipv4_addr_t dstAddr;
}

header udp_t {
    bit<16> srcPort;
    bit<16> dstPort;
    bit<16> length_;
    bit<16> checksum;
}

header source_routing_t {
    bit<1>  bos;
    bit<15> port;
}

/* -------- Parsed headers & metadata -------- */
const bit<16> ETHERTYPE_IPV4 = 0x0800;
const bit<16> ETHERTYPE_SR   = 0x1234;

struct headers_t {
    ethernet_t       ethernet;
    source_routing_t sr[8];
    ipv4_t           ipv4;
    udp_t            udp;
}

struct metadata_t {
    bit<1> has_srh;
}

/* -------- Parser -------- */
parser MyParser(packet_in packet,
                out headers_t hdr,
                inout metadata_t meta,
                inout standard_metadata_t standard_metadata)
{
    state start {
        meta.has_srh = 0;
        transition parse_ethernet;
    }

    state parse_ethernet {
        packet.extract(hdr.ethernet);
        transition select(hdr.ethernet.etherType) {
            ETHERTYPE_SR   : parse_srh_0;
            ETHERTYPE_IPV4 : parse_ipv4;
            default        : accept;
        }
    }

    state parse_srh_0 {
        packet.extract(hdr.sr[0]);
        meta.has_srh = 1;
        transition select(hdr.sr[0].bos) { 0: parse_srh_1; 1: parse_ipv4; }
    }
    state parse_srh_1 { packet.extract(hdr.sr[1]); transition select(hdr.sr[1].bos) { 0: parse_srh_2; 1: parse_ipv4; } }
    state parse_srh_2 { packet.extract(hdr.sr[2]); transition select(hdr.sr[2].bos) { 0: parse_srh_3; 1: parse_ipv4; } }
    state parse_srh_3 { packet.extract(hdr.sr[3]); transition select(hdr.sr[3].bos) { 0: parse_srh_4; 1: parse_ipv4; } }
    state parse_srh_4 { packet.extract(hdr.sr[4]); transition select(hdr.sr[4].bos) { 0: parse_srh_5; 1: parse_ipv4; } }
    state parse_srh_5 { packet.extract(hdr.sr[5]); transition select(hdr.sr[5].bos) { 0: parse_srh_6; 1: parse_ipv4; } }
    state parse_srh_6 { packet.extract(hdr.sr[6]); transition select(hdr.sr[6].bos) { 0: parse_srh_7; 1: parse_ipv4; } }
    state parse_srh_7 { packet.extract(hdr.sr[7]); transition parse_ipv4; }

    state parse_ipv4 {
        packet.extract(hdr.ipv4);
        transition select(hdr.ipv4.protocol) {
            17: parse_udp;
            default: accept;
        }
    }

    state parse_udp {
        packet.extract(hdr.udp);
        transition accept;
    }
}

/* -------- Ingress -------- */
control MyIngress(inout headers_t hdr,
                  inout metadata_t meta,
                  inout standard_metadata_t standard_metadata)
{
    action drop() {
        mark_to_drop(standard_metadata);
    }

    action ipv4_forward(mac_addr_t dstAddr, mac_addr_t srcAddr, egressSpec_t port) {
        hdr.ethernet.dstAddr = dstAddr;
        hdr.ethernet.srcAddr = srcAddr;
        hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
        standard_metadata.egress_spec = port;
        hdr.ethernet.etherType = ETHERTYPE_IPV4;
    }

    table ipv4_lpm {
        key = {
            hdr.ipv4.dstAddr: lpm;
        }
        actions = {
            ipv4_forward;
            drop;
        }
        size = 1024;
        default_action = drop();
    }

    apply {
        if (meta.has_srh == 1) {
            egressSpec_t p = (egressSpec_t) hdr.sr[0].port;

            // Decrement TTL on each SR hop
            hdr.ipv4.ttl = hdr.ipv4.ttl - 1;

            standard_metadata.egress_spec = p;

            // pop one segment
            hdr.sr.pop_front(1);

            // if no more SR headers, convert back to IPv4
            if (hdr.sr[0].isValid()) {
                hdr.ethernet.etherType = ETHERTYPE_SR;
            } else {
                hdr.ethernet.etherType = ETHERTYPE_IPV4;
            }

        } else if (hdr.ipv4.isValid()) {
            ipv4_lpm.apply();
        }
    }
}

/* -------- Egress -------- */
control MyEgress(inout headers_t hdr,
                 inout metadata_t meta,
                 inout standard_metadata_t standard_metadata)
{
    apply { }
}

/* -------- Checksums -------- */
control MyVerifyChecksum(inout headers_t hdr, inout metadata_t meta)
{
    apply { }
}

control MyComputeChecksum(inout headers_t hdr, inout metadata_t meta)
{
    apply {
        update_checksum(
            hdr.ipv4.isValid(),
            { hdr.ipv4.version,
              hdr.ipv4.ihl,
              hdr.ipv4.diffserv,
              hdr.ipv4.totalLen,
              hdr.ipv4.identification,
              hdr.ipv4.flags,
              hdr.ipv4.fragOffset,
              hdr.ipv4.ttl,
              hdr.ipv4.protocol,
              hdr.ipv4.srcAddr,
              hdr.ipv4.dstAddr },
            hdr.ipv4.hdrChecksum,
            HashAlgorithm.csum16);
    }
}

/* -------- Deparser -------- */
control MyDeparser(packet_out packet, in headers_t hdr)
{
    apply {
        packet.emit(hdr.ethernet);
        packet.emit(hdr.sr);
        packet.emit(hdr.ipv4);
        packet.emit(hdr.udp);
    }
}

/* -------- Main -------- */
V1Switch(
    MyParser(),
    MyVerifyChecksum(),
    MyIngress(),
    MyEgress(),
    MyComputeChecksum(),
    MyDeparser()
) main;